/**
 * elancom Corporation copy right 2012 - 2015  
 *   
 * $Header: https://192.168.1.224/svn/elancom/product/elancom-platform/trunk/src/main/java/com/elancom/framework/commons/json/parser/deserializer/DefaultObjectDeserializer.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Id: DefaultObjectDeserializer.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Author: lanzhouxue $
 * $Date: 2012-07-02 11:09:12 +0800 (Mon, 02 Jul 2012) $
 * $Revision: 1034 $
 */
package com.elancom.framework.commons.json.parser.deserializer;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.elancom.framework.commons.json.JSONException;
import com.elancom.framework.commons.json.parser.DefaultJSONParser;
import com.elancom.framework.commons.json.parser.DefaultJSONParser.ResolveTask;
import com.elancom.framework.commons.json.parser.Feature;
import com.elancom.framework.commons.json.parser.JSONLexer;
import com.elancom.framework.commons.json.parser.JSONScanner;
import com.elancom.framework.commons.json.parser.JSONToken;
import com.elancom.framework.commons.json.parser.ParseContext;
import com.elancom.framework.commons.json.util.ASMClassLoader;
import com.elancom.framework.commons.json.util.TypeUtils;

public class DefaultObjectDeserializer implements ObjectDeserializer {

	public final static DefaultObjectDeserializer instance = new DefaultObjectDeserializer();

	public DefaultObjectDeserializer() {
	}

	public Object parseMap(DefaultJSONParser parser, Map<Object, Object> map, Type keyType, Type valueType, Object fieldName) {
		final JSONScanner lexer = (JSONScanner) parser.getLexer();

		if (lexer.token() != JSONToken.LBRACE && lexer.token() != JSONToken.COMMA) {
			throw new JSONException("syntax error, expect {, actual " + lexer.tokenName());
		}

		final ObjectDeserializer keyDeserializer = parser.getConfig().getDeserializer(keyType);
		final ObjectDeserializer valueDeserializer = parser.getConfig().getDeserializer(valueType);
		lexer.nextToken(keyDeserializer.getFastMatchToken());

		final ParseContext context = parser.getContext();
		try {
			for (;;) {
				if (lexer.token() == JSONToken.RBRACE) {
					lexer.nextToken(JSONToken.COMMA);
					break;
				}

				if (lexer.token() == JSONToken.LITERAL_STRING && lexer.isRef()) {
					Object object = null;

					lexer.nextTokenWithColon(JSONToken.LITERAL_STRING);
					if (lexer.token() == JSONToken.LITERAL_STRING) {
						final String ref = lexer.stringVal();
						if ("@".equals(ref)) {
							object = context.getObject();
						} else if ("..".equals(ref)) {
							final ParseContext parentContext = context.getParentContext();
							if (parentContext.getObject() != null) {
								object = parentContext.getObject();
							} else {
								parser.addResolveTask(new ResolveTask(parentContext, ref));
								parser.setResolveStatus(DefaultJSONParser.NeedToResolve);
							}
						} else if ("$".equals(ref)) {
							ParseContext rootContext = context;
							while (rootContext.getParentContext() != null) {
								rootContext = rootContext.getParentContext();
							}

							if (rootContext.getObject() != null) {
								object = rootContext.getObject();
							} else {
								parser.addResolveTask(new ResolveTask(rootContext, ref));
								parser.setResolveStatus(DefaultJSONParser.NeedToResolve);
							}
						} else {
							parser.addResolveTask(new ResolveTask(context, ref));
							parser.setResolveStatus(DefaultJSONParser.NeedToResolve);
						}
					} else {
						throw new JSONException("illegal ref, " + JSONToken.name(lexer.token()));
					}

					lexer.nextToken(JSONToken.RBRACE);
					if (lexer.token() != JSONToken.RBRACE) {
						throw new JSONException("illegal ref");
					}
					lexer.nextToken(JSONToken.COMMA);

					// parser.setContext(context, map, fieldName);
					// parser.setContext(context);

					return object;
				}

				if (map.size() == 0 && lexer.token() == JSONToken.LITERAL_STRING && "@type".equals(lexer.stringVal())) {
					lexer.nextTokenWithColon(JSONToken.LITERAL_STRING);
					lexer.nextToken(JSONToken.COMMA);
					lexer.nextToken(keyDeserializer.getFastMatchToken());
				}

				final Object key = keyDeserializer.deserialze(parser, keyType, null);

				if (lexer.token() != JSONToken.COLON) {
					throw new JSONException("syntax error, expect :, actual " + lexer.token());
				}

				lexer.nextToken(valueDeserializer.getFastMatchToken());

				final Object value = valueDeserializer.deserialze(parser, valueType, key);

				if (map.size() == 0 && context != null && context.getObject() != map) {
					parser.setContext(context, map, fieldName);
				}

				map.put(key, value);

				if (lexer.token() == JSONToken.COMMA) {
					lexer.nextToken(keyDeserializer.getFastMatchToken());
				}
			}
		} finally {
			parser.setContext(context);
		}

		return map;
	}

	@SuppressWarnings("rawtypes")
	public Map parseMap(DefaultJSONParser parser, Map<String, Object> map, Type valueType, Object fieldName) {
		final JSONScanner lexer = (JSONScanner) parser.getLexer();

		if (lexer.token() != JSONToken.LBRACE) {
			throw new JSONException("syntax error, expect {, actual " + lexer.token());
		}

		final ParseContext context = parser.getContext();
		try {
			for (;;) {
				lexer.skipWhitespace();
				char ch = lexer.getCurrent();
				if (parser.isEnabled(Feature.AllowArbitraryCommas)) {
					while (ch == ',') {
						lexer.incrementBufferPosition();
						lexer.skipWhitespace();
						ch = lexer.getCurrent();
					}
				}

				String key;
				if (ch == '"') {
					key = lexer.scanSymbol(parser.getSymbolTable(), '"');
					lexer.skipWhitespace();
					ch = lexer.getCurrent();
					if (ch != ':') {
						throw new JSONException("expect ':' at " + lexer.pos());
					}
				} else if (ch == '}') {
					lexer.incrementBufferPosition();
					lexer.resetStringPosition();
					lexer.nextToken(JSONToken.COMMA);
					return map;
				} else if (ch == '\'') {
					if (!parser.isEnabled(Feature.AllowSingleQuotes)) {
						throw new JSONException("syntax error");
					}

					key = lexer.scanSymbol(parser.getSymbolTable(), '\'');
					lexer.skipWhitespace();
					ch = lexer.getCurrent();
					if (ch != ':') {
						throw new JSONException("expect ':' at " + lexer.pos());
					}
				} else {
					if (!parser.isEnabled(Feature.AllowUnQuotedFieldNames)) {
						throw new JSONException("syntax error");
					}

					key = lexer.scanSymbolUnQuoted(parser.getSymbolTable());
					lexer.skipWhitespace();
					ch = lexer.getCurrent();
					if (ch != ':') {
						throw new JSONException("expect ':' at " + lexer.pos() + ", actual " + ch);
					}
				}

				lexer.incrementBufferPosition();
				lexer.skipWhitespace();
				ch = lexer.getCurrent();

				lexer.resetStringPosition();

				if (key == "@type") {
					final String typeName = lexer.scanSymbol(parser.getSymbolTable(), '"');
					final Class<?> clazz = TypeUtils.loadClass(typeName);

					if (clazz == map.getClass()) {
						lexer.nextToken(JSONToken.COMMA);
						if (lexer.token() == JSONToken.RBRACE) {
							lexer.nextToken(JSONToken.COMMA);
							return map;
						}
						continue;
					}

					final ObjectDeserializer deserializer = parser.getConfig().getDeserializer(clazz);

					lexer.nextToken(JSONToken.COMMA);

					parser.setResolveStatus(DefaultJSONParser.TypeNameRedirect);
					return (Map) deserializer.deserialze(parser, clazz, fieldName);
				}

				Object value;
				lexer.nextToken();

				if (lexer.token() == JSONToken.NULL) {
					value = null;
					lexer.nextToken();
				} else {
					value = parser.parseObject(valueType);
				}

				map.put(key, value);
				parser.checkMapResolve(map, key);

				parser.setContext(context, value, key);

				if (lexer.token() == JSONToken.RBRACE) {
					lexer.nextToken();
					return map;
				}
			}
		} finally {
			parser.setContext(context);
		}

	}

	public void parseObject(DefaultJSONParser parser, Object object) {
		final Class<?> clazz = object.getClass();
		final Map<String, FieldDeserializer> setters = parser.getConfig().getFieldDeserializers(clazz);

		final JSONScanner lexer = (JSONScanner) parser.getLexer(); // xxx

		if (lexer.token() == JSONToken.RBRACE) {
			lexer.nextToken(JSONToken.COMMA);
			return;
		}

		if (lexer.token() != JSONToken.LBRACE && lexer.token() != JSONToken.COMMA) {
			throw new JSONException("syntax error, expect {, actual " + lexer.tokenName());
		}

		final Object[] args = new Object[1];

		for (;;) {
			// lexer.scanSymbol
			final String key = lexer.scanSymbol(parser.getSymbolTable());

			if (key == null) {
				if (lexer.token() == JSONToken.RBRACE) {
					lexer.nextToken(JSONToken.COMMA);
					break;
				}
				if (lexer.token() == JSONToken.COMMA) {
					if (parser.isEnabled(Feature.AllowArbitraryCommas)) {
						continue;
					}
				}
			}

			final FieldDeserializer fieldDeser = setters.get(key);
			if (fieldDeser == null) {
				if (!parser.isEnabled(Feature.IgnoreNotMatch)) {
					throw new JSONException("setter not found, class " + clazz.getName() + ", property " + key);
				}

				lexer.nextTokenWithColon();
				parser.parse(); // skip

				if (lexer.token() == JSONToken.RBRACE) {
					lexer.nextToken();
					return;
				}

				continue;
			} else {
				final Method method = fieldDeser.getMethod();
				final Class<?> fieldClass = method.getParameterTypes()[0];
				final Type fieldType = method.getGenericParameterTypes()[0];
				if (fieldClass == int.class) {
					lexer.nextTokenWithColon(JSONToken.LITERAL_INT);
					args[0] = IntegerDeserializer.deserialze(parser);
				} else if (fieldClass == String.class) {
					lexer.nextTokenWithColon(JSONToken.LITERAL_STRING);
					args[0] = StringDeserializer.deserialze(parser);
				} else if (fieldClass == long.class) {
					lexer.nextTokenWithColon(JSONToken.LITERAL_INT);
					args[0] = LongDeserializer.deserialze(parser);
				} else if (fieldClass == List.class) {
					lexer.nextTokenWithColon(JSONToken.LBRACE);
					args[0] = CollectionDeserializer.instance.deserialze(parser, fieldType, null);
				} else {
					final ObjectDeserializer fieldValueDeserializer = parser.getConfig().getDeserializer(fieldClass, fieldType);

					lexer.nextTokenWithColon(fieldValueDeserializer.getFastMatchToken());
					args[0] = fieldValueDeserializer.deserialze(parser, fieldType, null);
				}

				try {
					method.invoke(object, args);
				} catch (final Exception e) {
					throw new JSONException("set proprety error, " + method.getName(), e);
				}
			}

			if (lexer.token() == JSONToken.COMMA) {
				continue;
			}

			if (lexer.token() == JSONToken.RBRACE) {
				lexer.nextToken(JSONToken.COMMA);
				return;
			}
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
		if (type instanceof Class<?>) {
			return deserialze(parser, (Class<T>) type);
		}

		if (type instanceof ParameterizedType) {
			return (T) deserialze(parser, (ParameterizedType) type, fieldName);
		}

		if (type instanceof TypeVariable) {
			return (T) parser.parse(fieldName);
		}

		if (type instanceof WildcardType) {
			return (T) parser.parse(fieldName);
		}

		if (type instanceof GenericArrayType) {
			final Type componentType = ((GenericArrayType) type).getGenericComponentType();
			final List<Object> list = new ArrayList<Object>();
			parser.parseArray(componentType, list);
			if (componentType instanceof Class) {
				final Class<?> componentClass = (Class<?>) componentType;
				final Object[] array = (Object[]) Array.newInstance(componentClass, list.size());

				list.toArray(array);

				return (T) array;
			}
		}

		throw new JSONException("not support type : " + type);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public <T> T deserialze(DefaultJSONParser parser, ParameterizedType type, Object fieldName) {
		try {
			final JSONLexer lexer = parser.getLexer();
			if (lexer.token() == JSONToken.NULL) {
				lexer.nextToken();
				return null;
			}

			final Type rawType = type.getRawType();
			if (rawType instanceof Class<?>) {
				final Class<?> rawClass = (Class<?>) rawType;

				if (Map.class.isAssignableFrom(rawClass)) {
					Map map;

					if (Modifier.isAbstract(rawClass.getModifiers())) {
						if (rawClass == Map.class) {
							map = new HashMap();
						} else if (rawClass == SortedMap.class) {
							map = new TreeMap();
						} else if (rawClass == ConcurrentMap.class) {
							map = new ConcurrentHashMap();
						} else {
							throw new JSONException("can not create instance : " + rawClass);
						}
					} else {
						if (rawClass == HashMap.class) {
							map = new HashMap();
						} else {
							map = (Map) rawClass.newInstance();
						}
					}

					final Type keyType = type.getActualTypeArguments()[0];
					final Type valueType = type.getActualTypeArguments()[1];

					if (keyType == String.class) {
						return (T) parseMap(parser, map, valueType, fieldName);
					} else {
						return (T) parseMap(parser, map, keyType, valueType, fieldName);
					}
				}

			}

			throw new JSONException("not support type : " + type);
		} catch (final JSONException e) {
			throw e;
		} catch (final Throwable e) {
			throw new JSONException(e.getMessage(), e);
		}
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public <T> T deserialze(DefaultJSONParser parser, Class<T> clazz) {
		Object value = null;

		if (parser.getLexer().token() == JSONToken.NULL) {
			parser.getLexer().nextToken(JSONToken.COMMA);
			return null;
		}
		if (clazz.isAssignableFrom(HashMap.class)) {
			value = new HashMap();
		} else if (clazz.isAssignableFrom(TreeMap.class)) {
			value = new TreeMap();
		} else if (clazz.isAssignableFrom(ConcurrentHashMap.class)) {
			value = new ConcurrentHashMap();
		} else if (clazz.isAssignableFrom(Properties.class)) {
			value = new Properties();
		} else if (clazz.isAssignableFrom(IdentityHashMap.class)) {
			value = new IdentityHashMap();
		}

		if (clazz == Class.class) {
			final Object classValue = parser.parse();
			if (classValue == null) {
				return null;
			}

			if (classValue instanceof String) {
				return (T) ASMClassLoader.forName((String) classValue);
			}
		} else if (clazz == Serializable.class) {
			return (T) parser.parse();
		}

		if (value == null) {
			throw new JSONException("not support type : " + clazz);
		}

		try {
			parseObject(parser, value);
			return (T) value;
		} catch (final JSONException e) {
			throw e;
		} catch (final Throwable e) {
			throw new JSONException(e.getMessage(), e);
		}
	}

	@Override
	public int getFastMatchToken() {
		return JSONToken.LBRACE;
	}
}
